/*
 * main.cpp
 *
 *  Created on: 10/05/2013
 *      Author: gabriel
 */

#include "ModelViewer.hpp"
#include "shader.h"
#include "matrix4f.h"
#include "GLError.h"
#include "Light.hpp"
#include "HemisphericLight.h"
#include <sstream>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include "Ground.h"
#include "Skybox.h"
#include "Vehicle.h"
#include <vector>


#define TICK_MILLS 10
#define WINDOW_WIDTH 	640
#define WINDOW_HEIGHT 	480



GLvoid *font_style = GLUT_BITMAP_TIMES_ROMAN_24;
int frameCount = 0;
float fps = 0;
int currentTime = 0, previousTime = 0;


bool dragging = false;
float delta = 0.0f;

Shader *program;
Shader *axisProgram;
Shader *groundProgram;
Shader *vehicleProgram;
Shader *skyProgram;
//ModelViewer model;
ModelViewer sphere;
std::vector<ModelViewer> models;
Ground *ground;
Skybox *skybox;
Vehicle *car;

#define CAMERA_ROT_AMOUNT 3.0f
#define CAR_ROT_AMOUNT 10.0f
#define CAMERA_ZOOM_AMOUNT 0.5f
#define CAR_ZOOM_AMOUNT 1.0f
float cameraRotDelta = 0.0f;
float cameraRotDelta2 = 0.0f;
float cameraZoom = 0.0f;

float carRotDelta = 0.0f;
float carRotDeltaAnterior = 0.0f;
float carRotDelta2 = 0.0f;
float carZoom = 0.0f;

std::string fileName;

float prevMouseY;

unsigned int lineVao;

Light *light;
HemisphericLight *hLight;

int calculateFPS()
{

	frameCount++;

	currentTime = glutGet(GLUT_ELAPSED_TIME);

	int timeInterval = currentTime - previousTime;

	if(timeInterval > 1000)
	{
		fps = frameCount / (timeInterval / 1000.0f);
		previousTime = currentTime;

		frameCount = 0;
	}
	return fps;
}



int setUniformMatrix(unsigned int programHandle, const char *uniformLabel, float *matrix, int size) {

	glUseProgram(programHandle);

	int uniformHandle = glGetUniformLocation(programHandle, uniformLabel);

	if (uniformHandle == -1) {
		std::cout << "Uniform: " << uniformLabel << " was not an active uniform label\n";
		return 1;
	}


	if(size == 4){
		glUniformMatrix4fv( uniformHandle, 1, false, matrix );
	}else if(size == 3){
		glUniformMatrix3fv( uniformHandle, 1, false, matrix );
	}


	return 0;
}

void setupShaders(){

	Matrix4f projection;
	projection.loadPerspective(80, (double)WINDOW_WIDTH/(double)WINDOW_HEIGHT, 0.1, 150.0);

	setUniformMatrix(program->programHandle, "projection_matrix", projection.m,4);
	std::cout<<program->programHandle<<std::endl;
	setUniformMatrix(vehicleProgram->programHandle, "projection_matrix", projection.m,4);
	std::cout<<vehicleProgram->programHandle<<std::endl;
	setUniformMatrix(skyProgram->programHandle, "projection_matrix", projection.m,4);
	std::cout<<skyProgram->programHandle<<std::endl;

}


void setup()
{
	setupShaders();

	light = new Light();
	light->addShader(program);
	light->setPosition(0.0f, 0.0f, 0.0f, 0.0f);

	hLight = new HemisphericLight();
	hLight->setShader(vehicleProgram);
	float skyColour[4] = {0.0f, 0.0f, 1.0f, 0.0f};
	float groundColour[4] = {0.0f, 1.0f, 0.0f, 0.0f};
	hLight->setColour(skyColour, groundColour);
	hLight->setPosition(0.0f, 0.0f, 0.0f, 0.0f);

	std::cout<<"setup1"<<std::endl;
	ModelViewer carModel;
	carModel.load("models/car/car.obj", vehicleProgram);
	car = new Vehicle(carModel, 0.0f, 0.0f, 0.0f, vehicleProgram);


	skybox = new Skybox("skybox/sp3top.tga",
			"skybox/sp3bot.tga",
			"skybox/sp3left.tga",
			"skybox/sp3right.tga",
			"skybox/sp3back.tga",
			"skybox/sp3front.tga",
			skyProgram);

	ModelViewer *myModel = new ModelViewer();
	myModel->load("models/House2.obj",program);
	models.push_back(*myModel);


	myModel = new ModelViewer();
	myModel->load("models/Craft2.obj",program);
	models.push_back(*myModel);

	myModel = new ModelViewer();
	myModel->load("models/PineTree.obj",program);
	models.push_back(*myModel);

	ground = new Ground("models/Grass.tga", program);
	ground->setup();




}

void idle (void)
{

	calculateFPS();

	glutPostRedisplay ();
}


void render(){
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	Matrix4f modelMatrix, camera, axisMatrix, lightMatrix, sphereMatrix;
	Matrix4f matrix, modelsMatrix, originMatrix;


	camera.loadIdentity();
	camera.lookAt (car->x, car->y+ 5.0f, car->z - 10, // eye
			car->x, car->y, car->z,        // at
			0.0, 1.0, 0.0 );      // up


	ground->setMatrix(camera);
	ground->render();

	glUseProgram(program->programHandle);


	float scale = 100.0f;
	skybox->matrix.copy(camera);
	skybox->matrix.translate(0.0f, 10.0f, 0.0f);
	skybox->matrix.scale(scale,scale,scale);
	skybox->render();



	matrix.copy(camera);



	setUniformMatrix(program->programHandle, "modelview_matrix", matrix.m,4);
	setUniformMatrix(program->programHandle, "normal_matrix", matrix.getNormalMat().m,3);



	modelsMatrix.copy(camera);


	modelsMatrix.translate(-20.0f,-1.0f,-24.0f);

	for(int i =0 ;i< 2; i++){

		for(int j=0; j<15 ; j++){

			modelsMatrix.translate(2.5f, 0.0f, 0.0f);
			setUniformMatrix(program->programHandle, "modelview_matrix", modelsMatrix.m,4);
			setUniformMatrix(program->programHandle, "normal_matrix", modelsMatrix.getNormalMat().m,3);
			models.at(2).render(program);

		}
		modelsMatrix.translate(-(2.5*15) , 0.0f, 24+14.0f);
	}

	modelsMatrix.copy(camera);
	modelsMatrix.translate(-19.0f,-1.0f,-24.0f);
	for(int i =0 ;i< 2; i++){

		for(int j=0; j<15 ; j++){

			modelsMatrix.translate(0.0f, 0.0f, 2.5f);
			setUniformMatrix(program->programHandle, "modelview_matrix", modelsMatrix.m,4);
			setUniformMatrix(program->programHandle, "normal_matrix", modelsMatrix.getNormalMat().m,3);
			models.at(2).render(program);

		}
		modelsMatrix.translate(19*2 , 0.0f, -(2.5*15));
	}



	light->setPosition(0.0f, 3.0f, 0.0f, 0.0f);
	light->setMatrix(camera);
	light->enable();


	hLight->setPosition(0.0f, 10.0f, 0.0f, 0.0f);
	hLight->setMatrix(camera);
	hLight->enable();


	car->setMatrix(camera);
	car->render();


	glutSwapBuffers();
	glFlush();
}

void MouseFunc(int button, int state, int x, int y)
{
	switch (button)
	{
	case GLUT_RIGHT_BUTTON:
		if (state == GLUT_DOWN)
		{
			dragging = true;
			prevMouseY = y;
		} else {
			dragging = false;
		}
		break;
	default:
		break;
	}

}
void MotionFunc(int x, int y)
{

	if ( dragging ) {

		int newDelta = prevMouseY - y;
		if (newDelta<delta) {
			if(cameraZoom >= -40.0f){
				cameraZoom -= CAMERA_ZOOM_AMOUNT;
				delta = newDelta;
			}
		}
		if (newDelta>delta){
			if(cameraZoom <= 40.0f) {
				cameraZoom += CAMERA_ZOOM_AMOUNT;
				delta = newDelta;
			}
		}
	}
	glutPostRedisplay();


}
void keyboardDown(unsigned char key, int x, int y) {

	switch (key) {
	case 'a':
	case 'A':
		cameraRotDelta -= CAMERA_ROT_AMOUNT;
		break;
	case 'd':
	case 'D':
		cameraRotDelta += CAMERA_ROT_AMOUNT;
		break;
	case 'w':
	case 'W':
		cameraRotDelta2 += CAMERA_ROT_AMOUNT;
		break;
	case 'x':
	case 'X':
		cameraRotDelta2 -= CAMERA_ROT_AMOUNT;
		break;
	}
	glutPostRedisplay();

}

void specialFunc(int key, int x, int y){

	if(car->z > 12)
		car->z = 12;
	if(car->z < -20)
		car->z = -20;
	if(car->x > 15.5f)
		car->x = 15.5f;
	if(car->x < -15.5f)
		car->x = -15.5f;

	switch(key){
	case GLUT_KEY_LEFT:
		carRotDelta += CAR_ROT_AMOUNT;
		car->setTurnAngle(carRotDelta);
		break;
	case GLUT_KEY_RIGHT:
		carRotDelta -= CAR_ROT_AMOUNT;
		car->setTurnAngle(carRotDelta);
		break;
	case GLUT_KEY_UP:
		if(car->z < 12 && car->z > -20 && car->x <15.5f && car->x > -15.5f){
			carZoom = CAR_ZOOM_AMOUNT;
			car->setAcceleration(carZoom);
		}
		break;
	case GLUT_KEY_DOWN:
		if(car->z <= 12 && car->z >= -20 && car->x <=15.5f && car->x >= -15.5f){
			carZoom = -CAR_ZOOM_AMOUNT;
			car->setAcceleration(carZoom);
		}

		break;

	}
}

int main(int argc, char **argv)
{

	glutInit(&argc, argv);				
	glutInitWindowPosition(100, 100);		
	glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
	double bColour[3] = {0.2f, 0.2f, 0.2f};


	glutCreateWindow("Model_viewer");
	glClearColor(bColour[0], bColour[1], bColour[2], 1.0f);

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);


	glutKeyboardFunc(keyboardDown);

	glutMouseFunc(MouseFunc);
	glutMotionFunc(MotionFunc);
	glutIdleFunc (idle);
	glutSpecialFunc(specialFunc);


	program = new Shader("lightPerFrag.vp", "lightPerFrag.fp");

	vehicleProgram= new Shader("vehicle.vert", "vehicle.frag");

	skyProgram = new Shader("skyVert.vp", "skyFrag.fp");


	setup();


	glutDisplayFunc(render);

	glutMainLoop();

}







